package robocup.component;

import java.lang.reflect.Constructor;
import java.util.LinkedList;

import robocup.component.ControllerNumbers.PLAY_MODE;
import robocup.component.tactics.AbstractState;
import robocup.component.tactics.InterceptBallState;
import robocup.component.tactics.MyStateEval;
import robocup.component.tactics.StateEvaluation;
import robocup.component.tactics.AbstractState.STATES;
import robocup.component.worldobjects.Player;

/**
 * This is the class that loads all states for all players once an init info 
 * arrived. The states that should be loaded four the "self" are defined in the
 * agent-property-file.
 */
public class StateLoader{

  // the implemented team formations
  public final static int    TEAMFORMATION_442 = 1;

  public final static int    TEAMFORMATION_343 = 2;

  public final static int    TEAMFORMATION_541 = 3;

  public final static int    TEAMFORMATION_433 = 4;

  public final static int    ROLE_TACTIC_433   = 5;

  protected int              teamFormation     = StateLoader.TEAMFORMATION_433;

  private RobocupAgent       myMaster;

  private Prophet            prophet;

  /**
   * Constructor of the StateTactic, needs references of the agent
   * (RobocupAgent) and the prophet.
   * 
   * @param agent -
   *          the agent object (parent)
   * @param prophet -
   *          the predicting component
   */
  public StateLoader(
      final RobocupAgent client,
      final Prophet prophet) {

    this.myMaster = client;
    this.prophet = prophet;
  }

  /**
   * <code>setStates</code> sets all the states plus their allowed play-modes
   * of the conf file to the self player and adds the states to the state
   * evaluation.
   * 
   * @param strings -
   *          an array containing the string-names of the states. Note that the
   *          source-package of all states is <code>robocup.component.tactic
   * </code>,
   *          such that the state-names should be specified relative to that
   *          path.
   */
  public void setStates(final String[] strings) {

    final Player self = this.myMaster.getWorld().getSelfRef();
    final MyStateEval myStateEval = new MyStateEval(this.myMaster);

    PLAY_MODE pm;
    STATES lastState = null;

    for (final String key : strings) {

      try {
        pm = PLAY_MODE.valueOf(key);
      }
      catch (final IllegalArgumentException e) {
        pm = PLAY_MODE.UNKNOWN;
        // e.printStackTrace();
      }

      if (pm == PLAY_MODE.UNKNOWN) {
        Class<?> cls;
        try {
          cls = Class.forName("robocup.component.tactics." + key);
        }
        catch (final ClassNotFoundException e) {
          System.out.println("Invalid entry in state/playmode list: " + key);
          e.printStackTrace();
          continue;
        }
        // Got class, now get its constructor
        Constructor<?> constructor;
        try {
          // Is there a constuctor ?
          constructor = cls.getConstructor(StateEvaluation.class);
        }
        catch (final Exception e2) {
          System.out.println("Could not get constructor for state: " + key);
          e2.printStackTrace();
          continue;
        }
        AbstractState newState;
        try {
          newState = (AbstractState) constructor.newInstance(myStateEval);
          lastState = newState.getState();
          self.addKnownState(lastState, new LinkedList<PLAY_MODE>());
          myStateEval.addState(newState);
        }
        catch (final Exception e) {
          e.printStackTrace();
        }
      }
      else {
        if (lastState == null) {
          System.out.println(this.getAgent().getWorld().getCycleAndPlayerString() 
              + "StateTactic: Got PlayMode without state: " + pm);
          continue;
        }
        self.addPlayModeToState(lastState, pm);
      }
    }

    // set all stateEvaluations.
    // each player gets a set of known states+playmodes
    // TODO add states here (states have to be generic!!!)
    // TODO create a conf for all other players states?!
    for (final Player p : this.myMaster.getWorld().getAllPlayers()) {
      if (p.isMe()) {
        // take all that was written in the .conf file
        p.setMyStateEval(myStateEval);
      }
      else {
        final StateEvaluation othersStateEval =
            new StateEvaluation(this.myMaster);

        p.setMyStateEval(othersStateEval);

        if (p.isFriend()) {
          // teammates states
          othersStateEval.addState(new InterceptBallState(othersStateEval));
          p.addPlayModeToState(STATES.INTERCEPT_BALL, PLAY_MODE.PLAY_ON);
          p.addPlayModeToState(STATES.INTERCEPT_BALL,
              PLAY_MODE.META_STANDARD_OWN);

        }

      }
    }

  }

  /**
   * @return Returns the teamFormation.
   */
  public int getTeamFormation() {

    return this.teamFormation;
  }

  /**
   * Called when the init info is received, and invokes the creation of states
   * out of the property-file.
   */
  public void loadStates() {

    this.setStates(this.myMaster.getStates());
//    final int number = this.myMaster.getWorld().getNumber();
//    final int teamFormation = this.getTeamFormation();
//    final int ptype = FormationData.getPlayerTypeByNumber(number, teamFormation);
//    this.myMaster.getWorld().setPlayerType(ptype);
//    this.myMaster.getWorld().setTeamFormation(teamFormation);
  }

  /**
   * the robocup agent
   * 
   * @return
   */
  public RobocupAgent getAgent() {

    return this.myMaster;
  }

  /**
   * @return - the prophet
   */
  public Prophet getProphet() {

    return this.prophet;
  }
}
